<?php

namespace App\Console\Commands\Gii;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use PhpParser\Node;
use PhpParser\Node\Stmt\Expression;
use PhpParser\Node\Stmt\Function_;
use PhpParser\NodeDumper;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitorAbstract;
use PhpParser\ParserFactory;
use PhpParser\PrettyPrinter;
use PhpParser\PhpVersion;

/**
 * php think gii 应用名 表名(小驼峰) 模块名 -d 描述 -c 是否覆盖
 * php artisan app:gii admin systemAccounts '管理员' -D='管理员管理' -C=1
 */
class Gii extends Command
{
    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 
        'app:gii 
        {application : 应用名} 
        {tableName : 表名小驼峰} 
        {title : 模块名} 
        {--D|desc= : 描述可选} 
        {--U|userName= : 作者可选} 
        {--C|is_cover= : 是否覆盖可选}';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'gii 命令行';

    // 请求信息
	protected $title = '首页';	// 标题 采集
	protected $_isCover = false;// 是否覆盖
	protected $tableName;	// 表名- 下划线
	protected $fileName;	// 文件名-小驼峰(输入)
	protected $fileNameUp;	// 文件名-大驼峰(各种文件或者类名)
	protected $fileNameLow;	// 文件名-下划线(视图文件名等)
	protected $desc;		// 描述
	protected $userName;	// 作者
	protected $application;	// 应用名,如 admin 或者 api

	protected $date;
	protected $time;

	// 表字段信息
	public $tableFieldInfo = null;

    /**
     * Execute the console command.
     */
    public function handle()
    {
        $application = $this->argument('application');
        $tableName = $this->argument('tableName');
        $title = $this->argument('title');

		//验证参数
		if(empty($application)) return $this->_error('请提交应用名');
		if(empty($tableName)) return $this->_error('请提交表名');
		if(empty($title)) return $this->_error('请提交标题');

        $this->application = ucfirst($application);
		$this->tableName = $this->_getLower($tableName);
		$this->fileName = $tableName;
		$this->fileNameUp = ucfirst((string)$tableName);
		$this->fileNameLow = $tableName;

		$this->title = $title;

        $desc = $this->option('desc');
		$this->desc = empty($desc) ? '描述' : $desc;

        $userName = $this->option('userName');
		$this->userName = empty($userName) ? 'Gii' : $userName;

        $isCover = $this->option('is_cover');
        $this->_isCover = !empty($isCover);

		$this->date = date('Y/m/d');
		$this->time = date('H:i');

		$this->_exec();
    }

	// 实际执行
	private function _exec() {
		try {
			// 创建路由: 
			$this->_addRoute();
			

			//创建控制器
			$this->_createController();

			//创建自有模块
			$this->_createLogic();

			//创建公共模型
			$this->_createModel();

			//创建公共实体
			$this->_createEntity();

			//创建语言包
			$this->_createField();

			//创建列表视图
			$this->_createViewIndex();

			//创建表单视图
			$this->_createViewForm();
		} catch (\Exception $e) {
			$this->_error('error : ' . $e->getMessage());
			return ;
		}

		$this->info('created the program '. $this->application . '->'. $this->fileNameUp .' ok');
	}

	// 生成路由
	private function _addRoute() {
		$filePath = base_path('routes/web.php');

		// 初始化一个解析器
		$parser = (new ParserFactory)->createForVersion(PhpVersion::getHostVersion());
		
		// 将现有路由解析成 `抽象语法树`
		$phpCode = file_get_contents($filePath);
		$ast = $parser->parse($phpCode);

		// 整理要添加的代码
		$routeStubCode = file_get_contents($this->_getStubPath('route'));
		$dataReplace = [
			'{$desc}' => $this->desc,
			'{$application}'     => $this->application,
			'{$fileNameUp}'   => $this->fileNameUp,
			'{$fileName}' => $this->fileName,
		];
		$appendCode = strtr($routeStubCode, $dataReplace);

		// 将要添加的代码转为语法树
		$appendAst = $parser->parse($appendCode);

		// 合并两个语法树
		$ast = array_merge($ast, $appendAst);

		// 将语法树转为代码
		$prettyPrinter = new PrettyPrinter\Standard;
		$code = $prettyPrinter->prettyPrintFile($ast);

		// 保存
		file_put_contents($filePath, $code);
	}
    
	/**
	 * 创建表单视图
	 * @param $module
	 * @param $fileName
	 * @param $title
	 */
	private function _createViewForm() {
		$tableDesc = array_values($this->_getTableInfo($this->tableName));
		$fieldStr = '';
		unset($tableDesc[0]); //删除ID
		foreach($tableDesc as $field) {
			$fieldStr .= sprintf("echo %s->setRequire()->addVerify('required')->text('%s');\r\n        ",
				'$easyForm', $field->Field);
		}

		$data = [
			'{$application}'     => $this->application,
			'{$fileNameUp}'   => $this->fileNameUp,
			'{$fieldStr}'	=> $fieldStr,
		];

		//获取模板
		$stubPath = $this->_getStubPath('view_form');

		//获取目标文件
		$dir = resource_path('views/' . $this->application .'/' . substr($this->tableName, 0, -1) .'/');
		if(!realpath($dir)) mkdir($dir, 777, true);
		$filePath = $dir . 'form.blade.php';
		$this->_createBase($stubPath, $data, $filePath);
	}

	/**
	 * 创建列表视图
	 * @param $module
	 * @param $fileName
	 * @param $title
	 */
	private function _createViewIndex() {
		$tableDesc = $this->_getTableInfo($this->tableName);
		$fieldsStr = '';
		foreach($tableDesc as $field) {
			$status = '';
			if($field->Field == 'status') $status = ', toolbar:\'#toolStatus\'';
			$fieldsStr .= sprintf("{field:'%s', title:'<?php echo %s::%s ?>' %s},\r\n                ",
				$field->Field, $this->fileNameUp, $field->Field, $status);
		}

		$data = [
			'{$fileNameUp}' => $this->fileNameUp,
			'{$application}'=> $this->application,
			'{$fieldsStr}'	=> $fieldsStr,
		];

		//获取模板
		$stubPath = $this->_getStubPath('view_index');

		//获取目标文件
		$dir = resource_path('views/' . $this->application .'/' . substr($this->tableName, 0, -1) .'/');
		if(!realpath($dir)) mkdir($dir, 777, true);
		$filePath = $dir . 'index.blade.php';
		$this->_createBase($stubPath, $data, $filePath);
	}

	public function _createField() {
		$fieldList = $this->_getTableInfo($this->tableName);
		$fieldStr = '';
		foreach($fieldList as $field) {
			$fieldStr .= sprintf("const %s = '%s';\r\n    ", $field->Field, $field->Comment);
		}

		$data = [
			'{$desc}'  => $this->desc,
			'{$user}'  => $this->userName,
			'{$date}'  => $this->date,
			'{$time}'  => $this->time,
			'{$application}'	=> $this->application,
			'{$fileNameUp}'		=> $this->fileNameUp,
			'{$fieldStr}'		=> $fieldStr,
		];

		//获取模板
		$stubPath = $this->_getStubPath('field');

		//获取目标文件
		$dir = app_path('Http/' . $this->application .'/Models/Fields/');
		if(!realpath($dir)) mkdir($dir, 777, true);
		$filePath = $dir . $this->fileNameUp . '.php';
		$this->_createBase($stubPath, $data, $filePath);
	}

	/**
	 * 创建自有实体
	 * @param $module
	 * @param $fileName
	 * @param string $desc
	 * @param string $userName
	 * @param string $date
	 * @param string $time
	 */
	private function _createEntity() {
        $fieldList = $this->_getTableInfo($this->tableName);
        $functionStr = '';
        foreach($fieldList as $field) {
            $name = $this->_getCamelize($field->Field);
			$functionStr .= sprintf("    // 获取%s\r\n    public function get%s() {\r\n        return %s->%s;\r\n    }\r\n\r\n", $field->Comment, $name, '$this', $field->Field);
        }

        $data = [
            '{$desc}'  => $this->desc,
            '{$user}'  => $this->userName,
            '{$date}'  => $this->date,
            '{$time}'  => $this->time,
            '{$application}'	=> $this->application,
            '{$fileNameUp}'		=> $this->fileNameUp,
            '{$functionStr}'		=> $functionStr,
        ];

        //获取模板
        $stubPath = $this->_getStubPath('entity');

        //获取目标文件
        $dir = app_path('/Models/Entitys/');
        if(!realpath($dir)) mkdir($dir, 777, true);
        $filePath = $dir . $this->fileNameUp . 'Entity.php';
        $this->_createBase($stubPath, $data, $filePath);
	}

	/**
	 * 创建自有模型
	 * @param $module
	 * @param $fileName
	 * @param string $desc
	 * @param string $userName
	 * @param string $date
	 * @param string $time
	 */
	private function _createModel() {
		$data = [
			'{$desc}'       => $this->desc,
			'{$userName}'   => $this->userName,
			'{$date}'       => $this->date,
			'{$time}'       => $this->time,
			'{$application}'     => $this->application,
			'{$fileNameUp}'   => $this->fileNameUp,
		];

		//获取模板
		$stubPath = $this->_getStubPath('model');

		//获取目标文件
		$dir = app_path('Models/Models/');
		if(!realpath($dir)) mkdir($dir, 777, true);
		$filePath = $dir . $this->fileNameUp . '.php';
		
		$this->_createBase($stubPath, $data, $filePath);
	}

	/**
	 * 创建自有模块
	 * @param $module 模块名
	 * @param $fileName 文件名
	 * @param string $desc 描述
	 * @param string $userName 作者
	 * @param string $date 日期
	 * @param string $time 时间
	 */
	private function _createLogic() {
		$data = [
			'{$desc}'       => $this->desc,
			'{$userName}'   => $this->userName,
			'{$date}'       => $this->date,
			'{$time}'       => $this->time,
			'{$application}'     => $this->application,
			'{$fileNameUp}'   => $this->fileNameUp,
		];

		//获取模板
		$stubPath = $this->_getStubPath('logic');

		//获取目标文件
		$dir = app_path('Http/' . $this->application .'/Logics/');
		if(!realpath($dir)) mkdir($dir, 777, true);
		$filePath = $dir . $this->fileNameUp . 'Logic.php';
		$this->_createBase($stubPath, $data, $filePath);
	}

	/**
	 * 创建控制器
	 * @param $module 模块名
	 * @param $fileName 文件名
	 * @param $title 标题
	 * @param string $desc 描述
	 * @param string $userName 作者
	 * @param string $date 日期
	 * @param string $time 时间
	 */
	private function _createController() {
		$data = [
			'{$desc}'       => $this->desc,
			'{$userName}'   => $this->userName,
			'{$date}'       => $this->date,
			'{$time}'       => $this->time,
			'{$application}'     => $this->application,
			'{$fileNameUp}'   => $this->fileNameUp,
			'{$title}'      => $this->title
		];

		//获取模板
		$stubPath = $this->_getStubPath('controller');


		//获取目标文件
		$dir = 'Http/' . $this->application . '/Controllers/';
		$dir = app_path($dir);

		if(!realpath($dir)) mkdir($dir, 777, true);
		$filePath = $dir . $this->fileNameUp . 'Controller.php';

		$this->_createBase($stubPath, $data, $filePath);
	}

	private function _createBase($stubPath, $data, $filePath) {
		//获取数据
		$content = file_get_contents($stubPath);

		//整理变量
		$content = strtr($content, $data);

		//生成文件
		if(!$this->_isCover and is_file($filePath)) return true;
		file_put_contents($filePath, $content);
	}

	//获取模板文件
	private function _getStubPath($stubFileName) {
		return app_path('Console/Commands/gii/stubs/') . $stubFileName . '.stub';
	}

	//获取下划线
	private function _getLower($str) {
		return strtolower(preg_replace('/([a-z])([A-Z])/', "$1" . '_' . "$2", $str));
	}

    // 获取驼峰
    private function _getCamelize($str, $separator='_') {
        $str = $separator. str_replace($separator, " ", strtolower($str));
        return ucfirst(ltrim(str_replace(" ", "", ucwords($str)), $separator ));
    }

	private function _getTableInfo($fileName) {
		if(!$this->tableFieldInfo) {
			$query = sprintf("SHOW FULL COLUMNS FROM `%s`;", $fileName);
			$this->tableFieldInfo = DB::select($query);
		}
		return $this->tableFieldInfo;
	}

	private function _error($msg) {
		$this->error($msg);
		return false;
	}
}
